# POST请求参数获取
30 秒速记
POST数据在请求体里,不在URL上,ctx.query拿不到Koa不解析请求体:要么自己读ctx.req流,要么用@koa/bodyparser,结果放在ctx.request.body- 按
Content-Type选解析:application/json、application/x-www-form-urlencoded、multipart/form-data(文件上传)各不相同 - 一定要有大小上限(
bodyparser默认JSON1mb、表单56kb),解析完还要校验字段 - 请求体是流,只能读一次:前面的中间件读过了,后面就拿不到
POST 的参数在请求体里,而请求体在 Node 里是一个流,Koa 本身不帮你解析,ctx.query 只管 URL 上的参数。 拿数据有两条路:自己监听 ctx.req 的 data / end 把流读完再解析,或者用 @koa/bodyparser 这类中间件,结果放在 ctx.request.body。实际项目我都用中间件,它会按 Content-Type 选解析方式,还自带大小限制,JSON 默认 1mb、表单 56kb。文件上传是 multipart/form-data,bodyparser 不管,要用 koa-body 或 @koa/multer。不管哪种,解析出来的数据照样不可信,字段校验不能省。
理解「POST请求参数获取」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
用 @koa/bodyparser 接收 POST 数据的最小例子,JSON 和表单编码都能处理:
const Koa = require('koa')
const { bodyParser } = require('@koa/bodyparser') // 老项目常见的是 require('koa-bodyparser')
const app = new Koa()
app.use(bodyParser({ jsonLimit: '1mb', formLimit: '100kb' }))
app.use(async ctx => {
if (ctx.method !== 'POST') return
const { email } = ctx.request.body ?? {}
if (typeof email !== 'string' || !email.includes('@')) ctx.throw(400, 'email 不合法')
ctx.body = { ok: true, email }
})
app.listen(3000)
// curl -X POST localhost:3000 -H 'Content-Type: application/json' -d '{"email":"a@b.c"}'
// -> {"ok":true,"email":"a@b.c"}
// curl -X POST localhost:3000 -d 'email=a%40b.c' 表单编码,结果一样
选型上可以这么记:只收 JSON 和普通表单,用 @koa/bodyparser;要接文件上传,用 koa-body 或 @koa/multer;需要拿原始字节做签名校验(比如支付回调),确认解析中间件保留了原始正文,或者在这条路由上不用通用解析器,自己读流。
面试官追问
追问 1注册接口用 POST 提交,后端读 ctx.query.email 一直是空的,为什么?
表单字段在请求体里,ctx.query 只解析 URL 上的查询串。挂上 bodyparser 之后从 ctx.request.body.email 拿。
追问 2移动端从表单编码改成发 JSON,后端要改什么?
用 bodyparser 的话基本不用改,它看 Content-Type 自动选解析方式。如果是手写的按 &、= 拆分,JSON 文本会被拆成一堆乱字段,得按 Content-Type 分开处理,不认识的类型回 415。
追问 3挂了 bodyparser,上传文件时 ctx.request.body 还是空的,为什么?
bodyparser 只处理 json、form、text,不碰 multipart/form-data。文件上传用 koa-body(开 multipart: true)或 @koa/multer,同时限制单文件大小和文件类型。
追问 4有人发了个 50MB 的 JSON,服务内存一下涨上去了,怎么防?
bodyparser 超过 jsonLimit 会直接回 413,别把上限随手调得很大。手写解析就要在 data 里累计字节数,超了立刻停止接收并报错;Nginx 那层也可以用 client_max_body_size 先挡一道。
追问 5日志中间件读了一遍请求体,后面的 bodyparser 就拿不到数据了,为什么?
请求体是流,读完就没了,不能重复读。让 bodyparser 放在最前面,日志中间件读 ctx.request.body;要原始文本的话,bodyparser 会把它放在 ctx.request.rawBody。
# POST请求参数获取
# 原理
30 秒速记
Koa 2没封装POST解析,要从原生请求ctx.req上读流- 表单提交的正文长这样:
a=1&b=2&c=3,读完再解析成{ a: '1', b: '2', c: '3' } ctx.request/ctx.response是Koa封装的;ctx.req/ctx.res是Node原生的IncomingMessage/ServerResponse- 数据分块(
chunk)到达,要等end再解析;先收Buffer最后Buffer.concat - 响应走
ctx.body,别直接ctx.res.end(),会绕开Koa的响应流程
Koa 2 没有内置 POST 解析,原理就是从 Node 原生请求对象 ctx.req 上把请求体这个流读完,再按格式解析。 以表单为例,浏览器发过来的正文是 a=1&b=2&c=3 这样的字符串,读完后按 & 和 = 拆成 { a: '1', b: '2', c: '3' }。这里要分清两套对象:ctx.request、ctx.response 是 Koa 包装过的,平时都用它们;ctx.req、ctx.res 是原生的,读原始流时才碰 ctx.req。响应一定走 ctx.body,直接 ctx.res.end() 等于绕开了 Koa,外层中间件和错误处理都管不到了。
对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req,将POST表单数据解析成query string(例如:a=1&b=2&c=3),再将query string 解析成JSON格式(例如:{"a":"1", "b":"2", "c":"3"})
注意:ctx.request是context经过封装的请求对象,ctx.req是context提供的node.js原生HTTP请求对象,同理ctx.response是context经过封装的响应对象,ctx.res是context提供的node.js原生HTTP响应对象。
具体koa2 API文档可见 https://github.com/koajs/koa/blob/master/docs/api/context.md#ctxreq
面试官追问
追问 1ctx.request 和 ctx.req 是同一个对象吗?
不是。ctx.request 是 Koa 的封装,上面有 query、path,挂了 bodyparser 还有 body;ctx.req 是 Node 原生的 http.IncomingMessage,是个可读流。它俩的关系是 ctx.request.req === ctx.req。
追问 2为什么不能在第一个 data 事件里就开始解析?
请求体是分块到达的,稍大一点的表单可能分好几个 chunk,第一块可能只有一半。要累积到 end 触发再解析。
追问 3发 JSON 时在 data 里直接拼字符串,中文偶尔乱码,为什么?
一个中文字符的 UTF-8 编码占 3 个字节,可能正好被切在两个 chunk 之间,各自转字符串就坏了。先把 Buffer 收进数组,end 时 Buffer.concat(chunks).toString('utf8'),或者一开始就 ctx.req.setEncoding('utf8')。
追问 4业务里直接 ctx.res.end('ok') 返回,会出什么问题?
Koa 的响应流程被绕开了:外层中间件再设响应头会报 ERR_HTTP_HEADERS_SENT,日志里记的状态码也可能对不上。真要直接操作 ctx.res,官方的做法是先设 ctx.respond = false。
追问 5文件上传也能用「拼成字符串再拆」这套思路吗?
不能。
